﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using System.Windows.Input;

namespace Rabbit.Controls
{
    public class RichTextBoxEx : RichTextBox
    {

        #region dependency property, check whether there is content in rich text box
        public bool HasContent
        {
            get { return (bool)GetValue(HasContentProperty); }
            set { SetValue(HasContentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HasContent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HasContentProperty =
            DependencyProperty.Register("HasContent", typeof(bool), typeof(RichTextBoxEx), new UIPropertyMetadata(false));



        public bool AutoAddWhiteSpaceAfterTriggered
        {
            get { return (bool)GetValue(AutoAddWhiteSpaceAfterTriggeredProperty); }
            set { SetValue(AutoAddWhiteSpaceAfterTriggeredProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AutoAddWhiteSpaceAfterTriggered.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoAddWhiteSpaceAfterTriggeredProperty =
            DependencyProperty.Register("AutoAddWhiteSpaceAfterTriggered", typeof(bool), typeof(RichTextBoxEx), new UIPropertyMetadata(true));


        #endregion

        private List<Image> imageList = new List<Image>();

        public List<string> ContentAssistSource = new List<string>();
        public List<char> ContentAssistTriggers = new List<char>();
        
        #region constructure
        public RichTextBoxEx()
        {
            AllowDrop = true;
            this.Loaded += new RoutedEventHandler(RichTextBoxEx_Loaded);
        }

        void RichTextBoxEx_Loaded(object sender, RoutedEventArgs e)
        {
            //init the assist list box
            if (this.Parent.GetType() != typeof(Grid))
            {
                throw new Exception("this control must be put in Grid control");
            }

            if (ContentAssistTriggers.Count==0)
            {
                ContentAssistTriggers.Add('@');
            }

            (this.Parent as Grid).Children.Add(AssistListBox);
            AssistListBox.MaxHeight = 100;
            AssistListBox.MinWidth = 100;
            AssistListBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            AssistListBox.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            AssistListBox.Visibility = System.Windows.Visibility.Collapsed;
            AssistListBox.MouseDoubleClick += new MouseButtonEventHandler(AssistListBox_MouseDoubleClick);
            AssistListBox.PreviewKeyDown += new KeyEventHandler(AssistListBox_PreviewKeyDown);
        }

        void AssistListBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            //if Enter\Tab\Space key is pressed, insert current selected item to richtextbox
            if (e.Key == Key.Enter || e.Key == Key.Tab || e.Key == Key.Space)
            {
                InsertAssistWord();
                e.Handled = true;
            }
            else if (e.Key == Key.Back)
            {
                //Baskspace key is pressed, set focus to richtext box
                if (sbLastWords.Length>=1)
                {
                    sbLastWords.Remove(sbLastWords.Length - 1, 1);
                }
                this.Focus();
            }
        }

        void AssistListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            InsertAssistWord();
        }

        private bool InsertAssistWord()
        {
            bool isInserted = false;
            if (AssistListBox.SelectedIndex != -1)
            {
                string selectedString = AssistListBox.SelectedItem.ToString().Remove(0, sbLastWords.Length);
                if (AutoAddWhiteSpaceAfterTriggered)
                {
                    selectedString += " ";
                }
                this.InsertText(selectedString);
                isInserted = true;
            }
          
            AssistListBox.Visibility = System.Windows.Visibility.Collapsed;
            sbLastWords.Clear();
            IsAssistKeyPressed = false;
            return isInserted;
        }
        #endregion

        #region check richtextbox's document.blocks is available
        private void CheckMyDocumentAvailable()
        {
            if (this.Document == null)
            {
                this.Document = new System.Windows.Documents.FlowDocument();
            }
            if (Document.Blocks.Count == 0)
            {
                Paragraph para = new Paragraph();
                Document.Blocks.Add(para);
            }
        } 
        #endregion

        #region Infotext, which is used as "hint text"
        public string InfoText
        {
            get { return (string)GetValue(InfoTextProperty); }
            set { SetValue(InfoTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InfoText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InfoTextProperty =
            DependencyProperty.Register("InfoText",
            typeof(string),
            typeof(RichTextBoxEx),
            new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnInfoTextChanged)));

        static void OnInfoTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            RichTextBoxEx rtb = sender as RichTextBoxEx;
            if (rtb.Document == null)
            {
                rtb.Document = new FlowDocument();
            }
            if (rtb.Document.Blocks.Count == 0)
            {
                Paragraph para = new Paragraph();
                rtb.Document.Blocks.Add(para);
            }
            if (string.IsNullOrEmpty(e.NewValue.ToString()))
            {
                rtb.Document.Blocks.Clear();
            }
            else
            {
                rtb.Selection.Start.DeleteTextInRun(e.OldValue.ToString().Count());
                rtb.Selection.Start.InsertTextInRun(e.NewValue.ToString());
            }
        }
        #endregion

        #region Focus changed
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            if (this.realText == this.InfoText)
            {
                Document.Blocks.Clear();
            }

            HasContent = true;
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            if (this.realText == "" && this.GetImages().Count == 0)
            {
                Document.Blocks.Clear();
                OnInfoTextChanged(this, new DependencyPropertyChangedEventArgs(InfoTextProperty, InfoText, InfoText));
                HasContent = false;
            }
        }
        #endregion

        #region Insert Image
        //TODO to be refined
        public void InsertImage(Image image)
        {
            Focus();
            CheckMyDocumentAvailable();
            image.MaxWidth = 250;
            image.Stretch = System.Windows.Media.Stretch.None;
            (Document.Blocks.FirstBlock as Paragraph).Inlines.Add(Environment.NewLine);
            (Document.Blocks.FirstBlock as Paragraph).Inlines.Add(image);
            CaretPosition = Document.ContentStart;
        }

        //TODO to be refined
        public void InsertImage(string path)
        {
            //get the image size
           BitmapFrame bf=  BitmapFrame.Create(new Uri(path), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.StreamSource = File.OpenRead(path);
            bi.DecodePixelWidth = bf.PixelWidth>=600?600:bf.PixelWidth;
            bi.EndInit();
             
            Image image = new Image();  
            image.Source = bi;
            if (path.ToUpper().EndsWith("GIF"))
            {
                image.Tag = path;
            }
            InsertImage(image);
        }
#endregion

        #region Insert Text
        public void InsertText(string text)
        {
            Focus();
            CaretPosition.InsertTextInRun(text);
            TextPointer pointer = CaretPosition.GetPositionAtOffset(text.Length);
            if (pointer != null)
            {
                CaretPosition = pointer;
            }
        }
        #endregion

        #region Clear Conten
        public void ClearContent()
        {
            foreach (var item in GetImages())
            {
                if ((item.Source as BitmapImage).StreamSource != null)
                {
                    (item.Source as BitmapImage).StreamSource.Close();
                }
            }

            IsAssistKeyPressed = false;
            sbLastWords.Clear();
            AssistListBox.Visibility = System.Windows.Visibility.Collapsed;

            Document.Blocks.Clear();
            HasContent = false;
            OnInfoTextChanged(this, new DependencyPropertyChangedEventArgs(InfoTextProperty, InfoText, InfoText));
            GC.Collect(2, GCCollectionMode.Forced);
        }
	#endregion

        #region Get Images List
        public List<Image> GetImages()
        {
            List<Image> imageList = new List<Image>();
            TextPointer position = Document.ContentStart;
            TextRange tx = new TextRange(Document.ContentStart, Document.ContentEnd);

            while (position != null)
            {
                if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.EmbeddedElement)
                {
                    DependencyObject dp = position.GetAdjacentElement(LogicalDirection.Forward);
                    if (dp.GetType() == typeof(Image))
                    {
                        imageList.Add(dp as Image);
                    }
                }
                position = position.GetNextContextPosition(LogicalDirection.Forward);
            }

            return imageList;
        }
        #endregion

        #region Text
        private string realText
        {
            get
            {
                TextRange tr = new TextRange(Document.ContentStart, Document.ContentEnd);
                return tr.Text.Replace("\r\n", "");
            }
        }

        public string Text
        {
            get
            {
                TextRange tr = new TextRange(Document.ContentStart, Document.ContentEnd);
                string text = tr.Text.Replace("\r\n", "");
                if (text == InfoText)
                {
                    return "";
                }
                return tr.Text.Replace("\r\n", "");
            }
        } 
        #endregion

        #region Drag and drop
        protected override void OnDragEnter(DragEventArgs e)
        {
            base.OnDragEnter(e);

            if (e.Data.GetDataPresent("FileNameW"))
            {
                e.Effects = DragDropEffects.All;
                string imgSource = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];
                if (File.Exists(imgSource))
                {
                    FileInfo fi = new FileInfo(imgSource);
                    string allowedExtension = ".jpg,.jpeg,.png,.gif,.bmp";
                    if (allowedExtension.Split(',').ToList().Exists(m => m == fi.Extension.ToLower()))
                    {
                        DataObject data = new DataObject();
                        data.SetText(imgSource);
                        DragDrop.DoDragDrop(this, data, DragDropEffects.All);
                    }
                }
            }
        }

        protected override void OnDrop(DragEventArgs e)
        {
            base.OnDrop(e);
            if (e.Data.GetDataPresent(DataFormats.UnicodeText))
            {
                try
                {
                    string picturePath = e.Data.GetData(DataFormats.UnicodeText).ToString();
                    InsertImage(picturePath);
                }
                catch
                {

                }
            }
        } 
        #endregion

        #region Content Assist
        private bool IsAssistKeyPressed = false;
        private System.Text.StringBuilder sbLastWords = new System.Text.StringBuilder();
        private ListBox AssistListBox = new ListBox();

        protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
        { 
            if (!IsAssistKeyPressed)
            {
                base.OnPreviewKeyDown(e);
                return;
            }

            ResetAssistListBoxLocation();
            
            if (e.Key == System.Windows.Input.Key.Back)
            {
                if (sbLastWords.Length > 0)
                {
                    sbLastWords.Remove(sbLastWords.Length - 1, 1);
                    FilterAssistBoxItemsSource();
                }
                else
                {
                    IsAssistKeyPressed = false;
                    sbLastWords.Clear();
                    AssistListBox.Visibility = System.Windows.Visibility.Collapsed;
                }
            }

            //enter key pressed, insert the first item to richtextbox
            if ((e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Tab))
            {
                AssistListBox.SelectedIndex = 0;
                if (InsertAssistWord())
                {
                    e.Handled = true;
                }
            }

            if (e.Key == Key.Down)
            {
                AssistListBox.Focus();
            }
           
            base.OnPreviewKeyDown(e);
        }

        private void FilterAssistBoxItemsSource()
        {
            IEnumerable<string> temp = ContentAssistSource.Where(s => s.ToUpper().StartsWith(sbLastWords.ToString().ToUpper()));
            AssistListBox.ItemsSource = temp;
            AssistListBox.SelectedIndex = 0;
            if (temp.Count() == 0)
            {
                AssistListBox.Visibility = System.Windows.Visibility.Collapsed;
            }
            else
            {
                AssistListBox.Visibility = System.Windows.Visibility.Visible;
            }
        }

        protected override void OnTextInput(System.Windows.Input.TextCompositionEventArgs e)
        {
            base.OnTextInput(e);
            if (IsAssistKeyPressed == false && e.Text.Length == 1)
            {
                Debug.Print("OnTextInput, e.Text:" + e.Text);
                if (ContentAssistTriggers.Contains(char.Parse(e.Text)) )
                {
                    ResetAssistListBoxLocation();
                    IsAssistKeyPressed = true;
                    FilterAssistBoxItemsSource();
                    return;
                }
            }

            if (IsAssistKeyPressed)
            {
                sbLastWords.Append(e.Text);
                FilterAssistBoxItemsSource();
            }
        }

        private void ResetAssistListBoxLocation()
        {
            Rect rect = this.CaretPosition.GetCharacterRect(LogicalDirection.Forward);
            double left = rect.X >= 20 ? rect.X : 20;
            double top = rect.Y >= 20 ? rect.Y + 20 : 20;
            left += this.Padding.Left;
            top += this.Padding.Top;
            AssistListBox.SetCurrentValue(ListBox.MarginProperty, new Thickness(left, top, 0, 0));
        }
        #endregion

    }

}
